/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;

namespace STSdb.General.Compression
{
    public static class CountCompression
    {
        private static ulong[] table = { 0x80, 0x4000, 0x200000, 0x10000000, 0x800000000, 0x40000000000, 0x2000000000000, 0x100000000000000, 0x8000000000000000 };

        /// <summary>
        /// Returns number of bytes to fit value of count after CountCompression.
        /// </summary>
        public static int GetLength(long count)
        {
            int len = 0;
            while (len < table.Length && (ulong)count > table[len])
                len++;
            return len + 1;
        }

        /// <summary>
        /// Returns number of bytes to fit value of count after CountCompression. The difference is only in implementation
        /// </summary>
        public static int GetLength2(long count)
        {
            int len = 0;
            do
            {
                count >>= 7;
                len++;
            } while (count > 0);
            return len;
        }

        private static int InternalSerialize(long count, out byte[] buf)
        {
            int index = 9;
            buf = new byte[10];
            if (count == 0)
                return index;

            do
            {
                buf[index] = (byte)((count & 0x7F) | 0x80);
                count >>= 7;
                index--;
            } while (count > 0);
            buf[9] = (byte)(buf[9] & 0x7F);

            return ++index;
        }

        /// <summary>
        /// Compress value of count by CountCompression, and stores result in BinaryWriter
        /// </summary>
        /// <param name="count">Value for compression.</param>
        public static void Serialize(BinaryWriter writer, long count)
        {
            byte[] buf;
            int index = InternalSerialize(count, out buf);
            writer.Write(buf, index, buf.Length - index);
        }

        /// <summary>
        /// Compress value of count by CountCompression and return the result in byte array.
        /// </summary>
        /// <param name="count">Value for compression.</param>
        /// <returns>Returns byte array with compressed data.</returns>
        public static byte[] ToByteArray(this long count)
        {
            byte[] buf;
            int index = InternalSerialize(count, out buf);
            byte[] res = new byte[buf.Length - index];
            Buffer.BlockCopy(buf, index, res, 0, res.Length);
            return res;
        }

        /// <summary>
        /// Decompress a value compressed with CountCompression by successively reading bytes from BinaryReader.
        /// </summary>
        public static long Deserialize(BinaryReader reader)
        {
            long count = 0;
            byte val;
            do
            {
                val = reader.ReadByte();
                count <<= 7;
                count |= (byte)(val & 0x7F);
            } while ((val & 0x80) == 0x80);
            return count;
        }

        /// <summary>
        /// Decompress a value compressed with CountCompression by successively reading bytes from byte array
        /// </summary>
        public static long FromByteArray(byte[] bytes)
        {
            long count = 0;
            int index = 0;
            do
            {
                count <<= 7;
                count |= (byte)(bytes[index] & 0x7F);
            } while ((bytes[index++] & 0x80) == 0x80 && index < bytes.Length);
            return count;
        }

        /// <summary>
        /// Decompress a value compressed with CountCompression, as compressed bytes are passes on each iteration.
        /// </summary>
        /// <param name="count"></param>
        /// <param name="next"></param>
        /// <returns>Returns false if this byte is last</returns>
        public static bool IterateFromByte(ref long count, byte next)
        {
            count <<= 7;
            count |= (byte)(next & 0x7F);
            return ((next & 0x80) == 0x80);
        }
    }

    //TODO: can be implemented faster version, which is not so size optimizied
    /*
        The length compression formula is fairly simple. If the length (which is an unsigned integer) is 0x7F or less, it is represented as 1 byte; if the length is greater than 0x7F but no larger than 0x3FFF, it is represented as a 2-byte unsigned integer with the senior bit set. Otherwise, it is represented as a 4-byte unsigned integer with two senior bits set. Table summarizes this formula.
        Table The Length Compression Formula for the Blob
        Value Range	Compressed Size	Compressed Value
        0–0x7F	1 byte	<value>
        0x80–0x3FFF	2 bytes	0x8000 │ <value>
        0x4000–0x1FFFFFFF	4 bytes	0xC0000000 │ <value>
     */

    
}
